{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## MNIST Image Classification with an RNN Model\n",
    "\n",
    "### Build the dataset and dataloader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/rowel/anaconda3/envs/agents/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz\n",
      "Failed to download (trying next):\n",
      "HTTP Error 404: Not Found\n",
      "\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz to ./data/MNIST/raw/train-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 9912422/9912422 [00:04<00:00, 2202830.82it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./data/MNIST/raw/train-images-idx3-ubyte.gz to ./data/MNIST/raw\n",
      "\n",
      "Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz\n",
      "Failed to download (trying next):\n",
      "HTTP Error 404: Not Found\n",
      "\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz to ./data/MNIST/raw/train-labels-idx1-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 28881/28881 [00:00<00:00, 121115.10it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./data/MNIST/raw/train-labels-idx1-ubyte.gz to ./data/MNIST/raw\n",
      "\n",
      "Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz\n",
      "Failed to download (trying next):\n",
      "HTTP Error 404: Not Found\n",
      "\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw/t10k-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1648877/1648877 [00:03<00:00, 488386.54it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./data/MNIST/raw/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw\n",
      "\n",
      "Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz\n",
      "Failed to download (trying next):\n",
      "HTTP Error 404: Not Found\n",
      "\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 4542/4542 [00:00<00:00, 1158368.53it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torchvision\n",
    "from torchvision import transforms\n",
    "\n",
    "# Define the transformations to apply to the data\n",
    "transform = transforms.Compose([\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize((0.5,), (0.5,))\n",
    "])\n",
    "\n",
    "# Load the MNIST dataset\n",
    "train_dataset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)\n",
    "test_dataset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)\n",
    "\n",
    "# Create the data loaders\n",
    "batch_size = 64\n",
    "train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n",
    "test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Visualize samples from the dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA8YAAAPdCAYAAABIgHGZAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAcsJJREFUeJzt3XeYldXVN+A1FKmKIKJiQbEhirEgKjERKyrGoCLGxNjQJEYTGzFqREw0duz1VaxgjAUMlhcTIxh9oyB2VCIWNFgpIqLSz/dHPkkMPHvGwxRm9n1fl9eVnN9Z+9lzYM+ZxTMzq6JUKpUCAAAAMtWorjcAAAAAdUljDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNY0xgAAAGRNY7wCmjJlSlRUVMQll1xSbWuOHTs2KioqYuzYsdW2JlB1zjU0PM41NDzOdb40xtXk1ltvjYqKipgwYUJdb6VG/fGPf4wdd9wxWrVqFauuumr07NkzHnvssbreFtSIhn6u119//aioqFjmfxtvvHFdbw9qREM/1xERjz76aOyyyy7Rvn37WHXVVaNHjx5xxx131PW2oMY09HM9cuTI6N27d3Ts2DGaNWsW66yzTvTr1y8mTpxY11trUJrU9QaoP84+++z43e9+F/369YsjjjgiFixYEBMnToz33nuvrrcGlOHyyy+POXPmfO2xd955J84888zYc88962hXwPIYNWpU9O3bN3bcccc4++yzo6KiIu6+++447LDDYvr06XHSSSfV9RaBb+jll1+Otm3bxgknnBDt27ePDz/8MG6++ebo0aNHPPXUU/Gtb32rrrfYIGiMqZKnn346fve738WQIUO8qUID0bdv36UeO/fccyMi4kc/+lEt7waoDldffXWstdZa8dhjj0WzZs0iIuKnP/1pdOnSJW699Vbv4VAPnXXWWUs9dvTRR8c666wT1113XVx//fV1sKuGx7dS16L58+fHWWedFdtuu220adMmWrVqFd/5zndizJgxhTWXXXZZdOrUKVq0aBE777zzMr9lYtKkSdGvX79o165dNG/ePLp37x6jRo2qdD9ffPFFTJo0KaZPn17pcy+//PJYc80144QTTohSqbTUXSbIVX0+18ty5513xgYbbBA9e/Ysqx4agvp8rmfPnh1t27Zd0hRHRDRp0iTat28fLVq0qLQeGqr6fK6XpUOHDtGyZcuYNWtWWfUsTWNci2bPnh033XRT9OrVKy688MI4++yzY9q0adG7d+944YUXlnr+7bffHldeeWUcd9xxcfrpp8fEiRNj1113jY8++mjJc1555ZXYYYcd4rXXXovTTjsthgwZEq1atYq+ffvGyJEjk/sZP358bLbZZnH11VdXuve//vWvsd1228WVV14Zq6++eqy88sqx1lprVakWGrL6fK7/2/PPPx+vvfZa/PCHP/zGtdCQ1Odz3atXr3jllVdi0KBB8cYbb8Sbb74Z55xzTkyYMCFOPfXUb/xaQENRn8/1V2bNmhXTpk2Ll19+OY4++uiYPXt27LbbblWupxIlqsUtt9xSiojSM888U/ichQsXlubNm/e1xz755JPSGmusUTrqqKOWPPb222+XIqLUokWL0tSpU5c8Pm7cuFJElE466aQlj+22226lbt26lebOnbvkscWLF5d69uxZ2njjjZc8NmbMmFJElMaMGbPUY4MHD05+bDNnzixFRGm11VYrtW7dunTxxReX/vjHP5b22muvUkSUrr/++mQ91FcN+VwvyymnnFKKiNKrr776jWuhvmjo53rOnDml/v37lyoqKkoRUYqIUsuWLUv3339/pbVQXzX0c/2VTTfddMm5bt26denMM88sLVq0qMr1pLljXIsaN24cK620UkRELF68OGbOnBkLFy6M7t27x3PPPbfU8/v27Rtrr732kv/fo0eP2H777ePhhx+OiIiZM2fGY489Fv3794/PPvsspk+fHtOnT48ZM2ZE7969Y/LkyclfjNWrV68olUpx9tlnJ/f91bdNz5gxI2666aYYOHBg9O/fPx566KHo2rXrkp9JhBzV13P93xYvXhx33XVXbL311rHZZpt9o1poaOrzuW7WrFlssskm0a9fv/jDH/4Qw4YNi+7du8ehhx4aTz/99Dd8JaDhqM/n+iu33HJLjB49Oq699trYbLPN4ssvv4xFixZVuZ40v3yrlt12220xZMiQmDRpUixYsGDJ4xtssMFSz13WuJRNNtkk7r777oiIeOONN6JUKsWgQYNi0KBBy7zexx9//LVDXY6vfiapadOm0a9fvyWPN2rUKA4++OAYPHhwvPvuu7Heeust13WgvqqP5/q/Pf744/Hee+/5xTzw/9XXc3388cfH008/Hc8991w0avSv+x/9+/ePzTffPE444YQYN27ccl8D6qv6eq6/suOOOy753z/4wQ+W/EN2dc5czpnGuBYNGzYsjjjiiOjbt2/86le/ig4dOkTjxo3j/PPPjzfffPMbr7d48eKIiBg4cGD07t17mc/ZaKONlmvPEbHklwmsuuqq0bhx469lHTp0iIiITz75RGNMlurruf5vw4cPj0aNGsUhhxxS7WtDfVNfz/X8+fNj6NChceqppy5piiP+9Q/be++9d1x99dUxf/78JXfNICf19VwXadu2bey6664xfPhwjXE10RjXonvvvTc6d+4cI0aMiIqKiiWPDx48eJnPnzx58lKPvf7667H++utHRETnzp0j4l9veLvvvnv1b/j/a9SoUWy11VbxzDPPLPWG+v7770dExOqrr15j14cVWX091/9p3rx5cd9990WvXr2iY8eOtXJNWJHV13M9Y8aMWLhw4TK/tXLBggWxePFi33ZJturruU758ssv49NPP62TazdEfsa4Fn11t7VUKi15bNy4cfHUU08t8/n333//1342Yfz48TFu3LjYe++9I+Jfd2t79eoVN9xwQ3zwwQdL1U+bNi25n2/ya+IPPvjgWLRoUdx2221LHps7d24MHz48unbt6otpslWfz/VXHn744Zg1a5bZxfD/1ddz3aFDh1h11VVj5MiRMX/+/CWPz5kzJx544IHo0qWLkU1kq76e64h/fUv2f5syZUr89a9/je7du1daT9W4Y1zNbr755hg9evRSj59wwgmx7777xogRI2L//fePPn36xNtvvx3XX399dO3adZlzgTfaaKPYaaed4thjj4158+bF5ZdfHqutttrXxi1cc801sdNOO0W3bt3imGOOic6dO8dHH30UTz31VEydOjVefPHFwr2OHz8+dtlllxg8eHClP/j/05/+NG666aY47rjj4vXXX4/11lsv7rjjjnjnnXfigQceqPoLBPVQQz3XXxk+fHg0a9YsDjzwwCo9HxqChniuGzduHAMHDowzzzwzdthhhzjssMNi0aJFMXTo0Jg6dWoMGzbsm71IUM80xHMdEdGtW7fYbbfdYquttoq2bdvG5MmTY+jQobFgwYK44IILqv4CkVY3vwy74fnq18QX/ffPf/6ztHjx4tJ5551X6tSpU6lZs2alrbfeuvTggw+WDj/88FKnTp2WrPXVr4m/+OKLS0OG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ilddddVkfuGFFxZmxxxzTLJ2+vTphdkvfvGLZO2nn36azBsid4wBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsGddUA9Zbb73CbPPNNy973QkTJpRdC6QtWLAgmV933XW1tJOq69mzZ2G2yiqr1OJOoH7629/+VpgdeOCBydo11lijurez3Bo1Kr7fsf3225e97korrZTM995777LXfvnllwuzSZMmlb3uivg5Ozep99UTTjghWbv22mtX93bq1Prrr1+Y/fWvf03WpkZXVXZGUqMmH3nkkWRtjtwxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGvmGJdh5ZVXTubDhg0rzCoqKsq+7jnnnFN2bZs2bQqzFi1aJGu//PLLwuzTTz8te0/A8tl0003Lrh01alQ17gTqp4kTJ5Zdu9VWWxVmrVq1Ksw+//zzsq85YMCAZJ6avbzXXnuVfd333nsvmd94442F2SuvvJKsffDBBwuzefPmpTdGg1XZ37m6sNFGGxVmF110UbJ2jz32KMxatmyZrL377rsLs+OOOy5ZO2PGjGTO17ljDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZK2iVCqVqvTE5Rgz1NBsscUWyfyFF14oe+3/+7//K8x69+5dmP3kJz9JrvuLX/yiMNtggw2StW+//XZhtvfeeydr33jjjWSekyoetVrlXK/YVlpppWQ+efLkwqxRo/S/e6Y+jxnDVnXOdf220047FWZ/+9vfkrWpsUs//vGPC7P7778/ue61115bmP3sZz9L1qbcd999yfyKK64ozCZNmpSsnT59ell7WlE51w3XDjvskMxTI9EOPfTQwqx58+bJdVOj4Y499thk7ZNPPpnMqZqqnGt3jAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKw1qesN1EdnnXVWja09d+7cwuzOO+8szL7//e8n112e0QOpcU6PPPJIsna33XYrzKZMmVLuliAL++yzTzJfd911C7PKRs0YyQQRzz77bGH20ksvJWu33HLLwiw1GmnRokXJdVNn86qrrkrWnn/++YXZxx9/nKxdvHhxMof64rTTTivMfvOb3yRrW7VqVdY1K/s6e+211y7M1lxzzWRtu3btCrOZM2emN8Y34o4xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWTPHuAwHHnhgMl+emcGpub8rok6dOiXzfv36FWaXXHJJdW8HGpT11luv7NonnniiGncCfBMVFRWFWZMm6S+9jj766MLsT3/6U9l7ghXJSiutlMxTc8IvvPDCZO0uu+xSmH3++efJ2nfffbcwe/nllwuzRo3S9xo333zzwuyPf/xjsvbFF18szHr37p2snTZtWjLn69wxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsmZcU4Hu3bvX9RaAzB100EHJPDWGobJxFpCDrl27JvOzzjqrMEuNi6lJW2yxRWFmXBMNRZcuXZL5+PHjC7N58+Yla3//+98XZg8++GCydty4ccm8JgwYMCCZ33jjjYXZ7bffnqzde++9y9pTrtwxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGvmGBeYMGFCYdaoUfrfExYvXlzd26nUF198kcxTM9IuuOCCZG2nTp3K2lNExOeff152LeSgf//+hdm3v/3tZO0jjzxSmM2ZM6fsPUF90qNHj8LsnnvuSdauu+66hdmkSZOStT/84Q8Ls6uuuqowq+xcp2YvV1RUJGtLpVIyhxXFu+++m8zPP//8wqyyr1s/++yzsvZUV+64445kvtdeexVm3/nOd6p7O1lzxxgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiacU1l+Oijj5J5+/bta2kn/9ayZctkfuedd5a99vKMf7juuuvKroUcrLnmmoVZZaNZUmPloKFYddVVk3lqrEtqHFNExGuvvVaY7bnnnsna9957rzB7/vnnC7PKxjV997vfLcwqGxe5aNGiZA4rilmzZiXz3/zmN7WzkRXAaqutlsz32GOPwuzll1+u7u1kzR1jAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsmaOcRnOOeecZH7FFVfU0k7q3iWXXFLXW4AVXmr26AEHHFCYffLJJ8l1hwwZUvaeoL7Yeuutk/kuu+xSmFU2K7V///6FWWpOcUREmzZtCrO99947WZsyatSowsycYqif2rVrV5iNGzeu7HVPP/30smtZmjvGAAAAZE1jDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1oxrKsOwYcOS+Z577lmY9enTp7q3U+Peeuutwuy8886rxZ1A/bTddtsVZt/97ncLsz/96U/JdSsbRQMNwRlnnFF27ZVXXpnM33jjjcJs//33T9Z++9vfLsw23HDDwmzOnDnJdR999NFkDpRvjTXWSOZz584tzAYNGlSYbbrppsl1U+OaFi9enKzdeeedC7MXX3wxWcs3444xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWTPHuAyffvppMu/Xr19htvXWWydrDzzwwMLs2GOPLcxatWqVXHfatGmF2e9+97tk7fDhwwuz2bNnJ2uBiB49epRV9+qrr1bzTqD+qaioKLv25JNPTua/+MUvCrO2bduWfd2UyuaTP/XUUzVyXchFmzZtCrM111wzWXvnnXcWZptttlnZe0pZnnnrVC93jAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxVlEqlUpWeuBzjEoCIKh61WuVcV13jxo0Ls9VXXz1ZO2bMmMJsnXXWKcw6deqUXHfmzJnJnJrnXNe8jTbaKJn/4Q9/KMy23XbbZG3qtZo6dWqy9tJLLy3MXnjhhcLsiSeeSK67cOHCZE7Nc65rXuo9NSKidevWhdlBBx2UrD3++OMLs65duyZrmzQpnmS7YMGCwuz2229PrnvdddcVZs8//3yydkX8+1gfVeV1dMcYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArJljDLVkRZxD51xX3WGHHVaYXXzxxcna1Jzju+66qzD74Q9/WPnGqFPOdd1LzR1daaWVyl530aJFyXzevHllr82KzbmuebfeemsyT73nLo9p06Yl86FDhxZm9913X2H27LPPlr0naoc5xgAAAFAJjTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFkrnnEAwBITJ04szNq0aVP2upWNhAHSFi5cWFYG1J3WrVsn848//rgw+9vf/pasTY1VevTRR5O1M2bMSOY0bO4YAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkLWKUqlUqtITKypqei/QoFXxqNUq57p67Lnnnsl80KBBhdlJJ51UmE2YMKHsPVE7nGtoeJxraHiqcq7dMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALJmXBPUEuMfoOFxrqHhca6h4TGuCQAAACqhMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrFaVSqVTXmwAAAIC64o4xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFnTGK+ApkyZEhUVFXHJJZdU25pjx46NioqKGDt2bLWtCVSdcw0Nj3MNDY9znS+NcTW59dZbo6KiIiZMmFDXW6kx7733XvTv3z9WXXXVWGWVVeL73/9+vPXWW3W9LagxDf1cjxgxIg4++ODo3LlztGzZMjbddNM45ZRTYtasWXW9NagxDf1cR0Tcddddsc0220Tz5s1j9dVXjwEDBsT06dPreltQY3I4148++mjssssu0b59+1h11VWjR48ecccdd9T1thqUJnW9AeqHOXPmxC677BKffvppnHHGGdG0adO47LLLYuedd44XXnghVltttbreIvAN/eQnP4mOHTvGoYceGuutt168/PLLcfXVV8fDDz8czz33XLRo0aKutwh8Q9ddd138/Oc/j9122y0uvfTSmDp1alxxxRUxYcKEGDduXDRv3ryutwh8Q6NGjYq+ffvGjjvuGGeffXZUVFTE3XffHYcddlhMnz49TjrppLreYoOgMaZKrr322pg8eXKMHz8+tttuu4iI2HvvvWOLLbaIIUOGxHnnnVfHOwS+qXvvvTd69er1tce23XbbOPzww2P48OFx9NFH183GgLLMnz8/zjjjjPjud78bf/nLX6KioiIiInr27Bnf+9734sYbb4xf/OIXdbxL4Ju6+uqrY6211orHHnssmjVrFhERP/3pT6NLly5x6623aoyriW+lrkXz58+Ps846K7bddtto06ZNtGrVKr7zne/EmDFjCmsuu+yy6NSpU7Ro0SJ23nnnmDhx4lLPmTRpUvTr1y/atWsXzZs3j+7du8eoUaMq3c8XX3wRkyZNqtK3V917772x3XbbLWmKIyK6dOkSu+22W9x9992V1kNDVZ/P9X83xRER+++/f0REvPbaa5XWQ0NVX8/1xIkTY9asWXHwwQcvaYojIvbdd99o3bp13HXXXZVeCxqq+nquIyJmz54dbdu2XdIUR0Q0adIk2rdv77u7qpHGuBbNnj07brrppujVq1dceOGFcfbZZ8e0adOid+/e8cILLyz1/Ntvvz2uvPLKOO644+L000+PiRMnxq677hofffTRkue88sorscMOO8Rrr70Wp512WgwZMiRatWoVffv2jZEjRyb3M378+Nhss83i6quvTj5v8eLF8dJLL0X37t2Xynr06BFvvvlmfPbZZ1V7EaCBqa/nusiHH34YERHt27cvqx4agvp6rufNmxcRscwvlFu0aBHPP/98LF68uAqvADQ89fVcR/zrH7JfeeWVGDRoULzxxhvx5ptvxjnnnBMTJkyIU0899Ru/FhQoUS1uueWWUkSUnnnmmcLnLFy4sDRv3ryvPfbJJ5+U1lhjjdJRRx215LG33367FBGlFi1alKZOnbrk8XHjxpUionTSSScteWy33XYrdevWrTR37twljy1evLjUs2fP0sYbb7zksTFjxpQiojRmzJilHhs8eHDyY5s2bVopIkq/+93vlsquueaaUkSUJk2alFwD6qOGfK6LDBgwoNS4cePS66+/XlY9rOga8rmeNm1aqaKiojRgwICvPT5p0qRSRJQiojR9+vTkGlAfNeRzXSqVSnPmzCn179+/VFFRseQst2zZsnT//fdXWkvVuWNcixo3bhwrrbRSRPzrLuzMmTNj4cKF0b1793juueeWen7fvn1j7bXXXvL/e/ToEdtvv308/PDDERExc+bMeOyxx6J///7x2WefxfTp02P69OkxY8aM6N27d0yePDnee++9wv306tUrSqVSnH322cl9f/nllxERX/v2ja989Us8vnoO5Ka+nutlufPOO2Po0KFxyimnxMYbb/yN66GhqK/nun379tG/f/+47bbbYsiQIfHWW2/FE088EQcffHA0bdo0Irxfk6/6eq4j/vU1+CabbBL9+vWLP/zhDzFs2LDo3r17HHroofH0009/w1eCIn75Vi376s1q0qRJsWDBgiWPb7DBBks9d1lfmG6yySZLfqb3jTfeiFKpFIMGDYpBgwYt83off/zx1w51Ob76lqyvvkXrP82dO/drz4Ec1cdz/d+eeOKJGDBgQPTu3Tt+//vfV+vaUB/V13N9ww03xJdffhkDBw6MgQMHRkTEoYceGhtuuGGMGDEiWrduvdzXgPqqvp7r448/Pp5++ul47rnnolGjf93X7N+/f2y++eZxwgknxLhx45b7GmiMa9WwYcPiiCOOiL59+8avfvWr6NChQzRu3DjOP//8ePPNN7/xel/9nNDAgQOjd+/ey3zORhtttFx7joho165dNGvWLD744IOlsq8e69ix43JfB+qj+nqu/9OLL74Y++23X2yxxRZx7733RpMm3hrIW30+123atIk//elP8e6778aUKVOiU6dO0alTp+jZs2esvvrqseqqq1bLdaC+qa/nev78+TF06NA49dRTlzTFERFNmzaNvffeO66++uqYP3/+krvhlM9XP7Xo3nvvjc6dO8eIESO+9tsiBw8evMznT548eanHXn/99Vh//fUjIqJz584R8a+Dsfvuu1f/hv+/Ro0aRbdu3ZY5NH3cuHHRuXPnWHnllWvs+rAiq6/n+itvvvlm7LXXXtGhQ4d4+OGH3U2CqP/nOiJivfXWi/XWWy8iImbNmhXPPvtsHHjggbVybVgR1ddzPWPGjFi4cGEsWrRoqWzBggWxePHiZWZ8c37GuBY1btw4IiJKpdKSx8aNGxdPPfXUMp9///33f+1nE8aPHx/jxo2LvffeOyIiOnToEL169YobbrhhmXdzp02bltzPN/k18f369Ytnnnnma83xP/7xj3jsscfioIMOqrQeGqr6fK4//PDD2HPPPaNRo0bxyCOPxOqrr15pDeSgPp/rZTn99NNj4cKFZp2Stfp6rjt06BCrrrpqjBw5MubPn7/k8Tlz5sQDDzwQXbp08SON1cQd42p28803x+jRo5d6/IQTToh99903RowYEfvvv3/06dMn3n777bj++uuja9euMWfOnKVqNtpoo9hpp53i2GOPjXnz5sXll18eq6222td+Lfs111wTO+20U3Tr1i2OOeaY6Ny5c3z00Ufx1FNPxdSpU+PFF18s3Ov48eNjl112icGDB1f6g/8///nP48Ybb4w+ffrEwIEDo2nTpnHppZfGGmusEaecckrVXyCohxrqud5rr73irbfeilNPPTWefPLJePLJJ5dka6yxRuyxxx5VeHWgfmqo5/qC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q3TkkUcma7331R53jAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMhaRamKA70qKipqei9ZWHXVVZP5OuusU9a6lc1WPu6448patzI33HBDMp8wYUJhNnny5GTtvHnzytrTiqquZuelONf/tv766yfzq666qjD77ne/m6xt3bp1OVuqM5Wdvb/+9a+F2emnn56snThxYll7WlE51/lKzR69/vrrC7NNNtkkue4777xT9p6oHs519VhttdUKs2effTZZW+7XwxHp16qm/my//PLLZH7YYYcVZuYU146q/Nm7YwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNY0xgAAAGTNuCaoJcY/NFx9+vRJ5qusskph9vOf/7wwu/baa8ve0/KYO3duMjda4t+c63z9z//8T2H28ssvF2ap0W+sGJzrmpca5RQR0aJFi8Ls6KOPTtautdZahdmAAQOStX/4wx8Ks4cffrgwGzt2bHLdDz74IJlT84xrAgAAgEpojAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKyZYwy1xFxEaHic64arSZMmyTw17/Scc84pzF566aWy90TtcK6h4THHGAAAACqhMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGvpWQQAABlavHhxMn/hhRcKMyOZAOofd4wBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADIWkWpVCpV6YkVFTW9F2jQqnjUapVzDcvHuYaGx7mGhqcq59odYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGtVHtcEAAAADZE7xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkTWO8ApoyZUpUVFTEJZdcUm1rjh07NioqKmLs2LHVtiZQdc41NDzONTQ8znW+NMbV5NZbb42KioqYMGFCXW+lRqy//vpRUVGxzP823njjut4e1IiGfq7/2x577BEVFRVx/PHH1/VWoMY09HM9YsSIOPjgg6Nz587RsmXL2HTTTeOUU06JWbNm1fXWoMY09HM9cuTI6N27d3Ts2DGaNWsW66yzTvTr1y8mTpxY11trUJrU9QaoHy6//PKYM2fO1x5755134swzz4w999yzjnYFVJcRI0bEU089VdfbAJbTT37yk+jYsWMceuihsd5668XLL78cV199dTz88MPx3HPPRYsWLep6i8A39PLLL0fbtm3jhBNOiPbt28eHH34YN998c/To0SOeeuqp+Na3vlXXW2wQNMZUSd++fZd67Nxzz42IiB/96Ee1vBugOs2dOzdOOeWU+PWvfx1nnXVWXW8HWA733ntv9OrV62uPbbvttnH44YfH8OHD4+ijj66bjQFlW9Z789FHHx3rrLNOXHfddXH99dfXwa4aHt9KXYvmz58fZ511Vmy77bbRpk2baNWqVXznO9+JMWPGFNZcdtll0alTp2jRokXsvPPOy/yWiUmTJkW/fv2iXbt20bx58+jevXuMGjWq0v188cUXMWnSpJg+fXpZH8+dd94ZG2ywQfTs2bOsemgIGsK5vuiii2Lx4sUxcODAKtdAQ1afz/V/N8UREfvvv39ERLz22muV1kNDVZ/P9bJ06NAhWrZs6cckqpHGuBbNnj07brrppujVq1dceOGFcfbZZ8e0adOid+/e8cILLyz1/Ntvvz2uvPLKOO644+L000+PiRMnxq677hofffTRkue88sorscMOO8Rrr70Wp512WgwZMiRatWoVffv2jZEjRyb3M378+Nhss83i6quv/sYfy/PPPx+vvfZa/PCHP/zGtdCQ1Pdz/e6778YFF1wQF154oW+xhP+vvp/r//bhhx9GRET79u3LqoeGoCGc61mzZsW0adPi5ZdfjqOPPjpmz54du+22W5XrqUSJanHLLbeUIqL0zDPPFD5n4cKFpXnz5n3tsU8++aS0xhprlI466qglj7399tuliCi1aNGiNHXq1CWPjxs3rhQRpZNOOmnJY7vttlupW7dupblz5y55bPHixaWePXuWNt544yWPjRkzphQRpTFjxiz12ODBg7/xx3vKKaeUIqL06quvfuNaqC9yONf9+vUr9ezZc8n/j4jScccdV6VaqI9yONf/bcCAAaXGjRuXXn/99bLqYUWXy7nedNNNSxFRiohS69atS2eeeWZp0aJFVa4nzR3jWtS4ceNYaaWVIiJi8eLFMXPmzFi4cGF07949nnvuuaWe37dv31h77bWX/P8ePXrE9ttvHw8//HBERMycOTMee+yx6N+/f3z22Wcxffr0mD59esyYMSN69+4dkydPjvfee69wP7169YpSqRRnn332N/o4Fi9eHHfddVdsvfXWsdlmm32jWmho6vO5HjNmTNx3331x+eWXf7MPGhq4+nyu/9udd94ZQ4cOjVNOOcUUCbLWEM71LbfcEqNHj45rr702Nttss/jyyy9j0aJFVa4nzS/fqmW33XZbDBkyJCZNmhQLFixY8vgGG2yw1HOX9Qa2ySabxN133x0REW+88UaUSqUYNGhQDBo0aJnX+/jjj792qKvD448/Hu+9916cdNJJ1bou1Ff18VwvXLgwfvnLX8aPf/zj2G677ZZrLWiI6uO5/m9PPPFEDBgwIHr37h2///3vq3VtqI/q+7necccdl/zvH/zgB0tuUFXnzOWcaYxr0bBhw+KII46Ivn37xq9+9avo0KFDNG7cOM4///x48803v/F6ixcvjoiIgQMHRu/evZf5nI022mi59rwsw4cPj0aNGsUhhxxS7WtDfVNfz/Xtt98e//jHP+KGG26IKVOmfC377LPPYsqUKUt+sQfkpr6e6//04osvxn777RdbbLFF3HvvvdGkiS/5yFtDONf/qW3btrHrrrvG8OHDNcbVxGfJWnTvvfdG586dY8SIEVFRUbHk8cGDBy/z+ZMnT17qsddffz3WX3/9iIjo3LlzREQ0bdo0dt999+rf8DLMmzcv7rvvvujVq1d07NixVq4JK7L6eq7ffffdWLBgQXz7299eKrv99tvj9ttvj5EjRy5zVBs0dPX1XH/lzTffjL322is6dOgQDz/8cLRu3brGrwkruvp+rpflyy+/jE8//bROrt0Q+RnjWtS4ceOIiCiVSkseGzduXDz11FPLfP7999//tZ9NGD9+fIwbNy723nvviPjXr2nv1atX3HDDDfHBBx8sVT9t2rTkfsr5NfEPP/xwzJo1y+xi+P/q67n+wQ9+ECNHjlzqv4iIffbZJ0aOHBnbb799cg1oqOrruY7412+g3nPPPaNRo0bxyCOPxOqrr15pDeSgPp/rjz/+eKnHpkyZEn/961+je/fuldZTNe4YV7Obb745Ro8evdTjJ5xwQuy7774xYsSI2H///aNPnz7x9ttvx/XXXx9du3aNOXPmLFWz0UYbxU477RTHHntszJs3Ly6//PJYbbXV4tRTT13ynGuuuSZ22mmn6NatWxxzzDHRuXPn+Oijj+Kpp56KqVOnxosvvli41/Hjx8cuu+wSgwcPrvIP/g8fPjyaNWsWBx54YJWeDw1BQzzXXbp0iS5duiwz22CDDdwppsFriOc6ImKvvfaKt956K0499dR48skn48knn1ySrbHGGrHHHntU4dWB+qmhnutu3brFbrvtFltttVW0bds2Jk+eHEOHDo0FCxbEBRdcUPUXiCSNcTW77rrrlvn4EUccEUcccUR8+OGHccMNN8QjjzwSXbt2jWHDhsU999wTY8eOXarmsMMOi0aNGsXll18eH3/8cfTo0SOuvvrqWGuttZY8p2vXrjFhwoT47W9/G7feemvMmDEjOnToEFtvvXWcddZZ1fqxzZ49Ox566KHo06dPtGnTplrXhhVZQz7XkKuGeq6/+kL8oosuWirbeeedNcY0aA31XB977LHx0EMPxejRo+Ozzz6LDh06xJ577hlnnHFGdOvWrdquk7uK0n9+PwEAAABkxs8YAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkLUmVX1iRUVFTe4DGrwVcWS4cw3Lx7mGhse5hoanKufaHWMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAga03qegMAAPxL8+bNC7OmTZsmaw8++ODCbPDgwcnap556qjDr379/shagIXDHGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJpxTQAAtaRjx47J/C9/+Uthttlmm1X3dpYolUo1tjZAfeCOMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFkzxxgAoBo1b968MEvNKY6o2VnFkINtttmmMDv33HOTtXvvvXdZ15w1a1YyP/vsswuzq666Klm7ePHiMnZEOdwxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsmZcEwBANVq0aFFhNn78+GStcU2QttVWWyXzP//5z4VZ27Ztk7VffPFFYfbee+8VZuuvv35y3csuu6wwW2mllZK1F198cTKn+rhjDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNbMMQZogNZZZ51kfvbZZxdmRx11VLL2iSeeKMx23nnnZC3kYMGCBYXZz372s2TtH//4x8Jsww03TNZeddVV6Y1BPbHyyisXZqmZwBERrVu3LszOOeecZG3qvTHlyCOPTOZDhw4tzHr27FnWNal+7hgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZq9fjmtZcc81kvssuuxRm1157bbL28ccfL8zmzZuXrH3wwQcLs/fffz9Z+9e//jWZA3xl8803L8xGjBiRrN1oo40Ks1KplKzt1q1bYfbaa68laz/55JPCzMgKclDZ1xCjR48uzFJjaCIiDj300MJs++23T28MViCpv8uVjQU84YQTCrOaGmn28ssvl1375ZdfVuNOWB7uGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJC1ej3H+Pzzz0/mhx9+eNlr77fffmXXHnTQQWXXLlq0qDB75ZVXCrNLL700ue6oUaMKs1mzZlW6L6BYp06dkvk777xTmK288sqF2SWXXJJc95hjjinMKptFvDzatGlTmKU+h0VEjB8/vrq3A9lYZ511knnXrl1raSdQs/785z8XZs8991yy9qGHHqru7VTqe9/7Xtm1zz//fDXuhOXhjjEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJC1ej2uafDgwcm8Xbt2hdnee++drG3SpG5emsaNGxdmW265ZWF26623JtedP39+YfaXv/wlWXvKKacUZq+//nqyFhqKyy+/vDA78sgjk7WTJ08uzDp27FiYrbHGGpXua0VT2efWCRMm1NJOoOFp3rx5Mk+Nf4P65M033yzMunfvXos7+be11167MNt///3LXveNN94ou5bq5Y4xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWavXc4zffffdZP7973+/MGvdunWytqKiojDbb7/9krWpuaS77757sja1rx133DFZm7LSSisVZn369EnW7rrrroXZ1Vdfnaz99a9/nd4Y1BPbbLNNYVbZ55Ott966urdTp1Lzy2fPnl2LO4G8bLfddjW2thnjELHKKqsUZpdccklhtsUWWyTXffDBBwuz//3f/618Y9QKd4wBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsVZRKpVKVnpgYX0T1ady4cWG29tprF2aXX355ct3ddtutMFt55ZUr3VeRyv76pMZTjRkzpuzr1kdVPGq1yrn+t9NPPz2Zn3feeYVZXf3Zpv78Pvzww2TtGmusUfZ1jzrqqMLs1ltvLXvd+si5prq1a9euMHv88ceTtZtvvnnZ102NpHvhhRfKXrc+cq7rtyZNiqfRpka5RkRcdtllhdk666xT9p5efvnlwuzEE09M1ub29XJNqcq5dscYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArBUP+qJOLFq0qDB79913C7MDDjgguW5qBvLIkSOTtd27dy/MKpurl5olB7UtNb/wjDPOSNam5t/V1MzL4cOHJ/Pf//73hVllc5l//OMfl7WniIh33nmn7FrIXaNG6XsSV1xxRWG2xRZblH3dX//618k8t1nF1F+77LJLMj/qqKMKsx/96EfVvZ2IiPjggw+Sebdu3QqzUaNGJWvvueeewqyyr10+/PDDZM7XuWMMAABA1jTGAAAAZE1jDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkzSydTLz33nuF2SGHHJKsnTx5ctnXPeKIIwqzv/zlL2WvC8uy1lprJfNzzjmnMGvZsmV1b6dK7rrrrsLsxBNPTNbOnDmzMKvstUj5+9//nsw/+eSTsteGFcnqq69emC3PuMGVVlqpMDvrrLOStalxMpWNhps/f35hNnbs2GQtrEhatWpVmA0dOjRZu/766xdmqTMSEfHggw8WZvfee29hNnr06OS6W265ZWGWGr0Ykf5aurKxqcccc0xhtnDhwmRtjtwxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsmZcE/HOO+8k8wkTJhRm3bt3T9b269evMEuNzomImDRpUjKH/3bssccm880337yWdvJ1V1xxRWE2cODAwmzx4sXJdddYY43CbIsttqh8YwUqG+vywgsvlL02VLd11lmnMDv++OOTtanPGSuvvHLZe0qNUKls5NLyeOmllwqzl19+ucauC9Vt3rx5hdm4ceOStR9++GFhdvHFFydrR44cmd5Ymf72t78VZqmRShHpUVCHH354svbRRx8tzIYPH56szZE7xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNY0xgAAAGTNHGOS8xYjIpo0Kf+vSWpG8oIFC8pel3z96Ec/Kswqm2NcU+bOnZvMU3/XK5tBmLLmmmsWZqusskrZ6+60007J/MgjjyzMJk+enKxNzVL99NNPk7WpmYvXXXddsvbJJ59M5tStli1bFma77757snbo0KGF2WqrrVb2nuqj7t27F2b77LNPsva+++6r7u1A2RYuXFiYHXLIIbW4k5o3adKkZD5ixIjC7MQTT0zWrrfeeuVsKVvuGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFmrKJVKpSo9sZKRPtRfa6+9djL/5z//WfbaTz31VGH27W9/u+x166MqHrVatSKe6/333z+Z19VIkdRrVVd/tvVtTxERw4YNK8wmTpyYrD3zzDMLsx133DFZW9na5XKuq8d+++1XmN1///1lr/vEE08k8/PPP78wO+6445K1ffr0KcxWxLM5ffr0ZH7ZZZcVZqnXqSFyrlmR7LnnnoXZ6NGjk7V//etfC7M99tij7D3VR1U51+4YAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkLUmdb0B6t5BBx1UY2tfeumlNbY2DdOxxx6bzFfE+ZL2VHU//OEPy64dPHhwYVZTc4qpumbNmhVmlb0X9O/fv+zrjhs3rjA766yzyl7329/+dtm1Kc8880wyP+WUUwqzww8/PFk7YMCAwqx9+/bJ2pNOOqkw22effZK13//+9wuzmTNnJmuBtM8//7zs2tTnsTZt2iRrP/3007KvW1+5YwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNY0xgAAAGTNuCbi4IMPrrG1Z8+eXWNr0zD98pe/TOap0SyV/X377W9/W5hVNiZq6623Tub1zfvvv1+YVTZOJuXvf/97Mn/99dfLXnv06NFl11LzevbsWZhVdr5SjjvuuGR+xx13FGZHHXVUsvaiiy4qzFZaaaX0xhK+/PLLwmzfffdN1k6bNq0wmzp1arK2T58+hdmaa66ZrE2Nc6ps1NPyvFZA2lZbbVV2ber9/LPPPit73YbKHWMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACyZo5xJnbZZZfCbIsttih73Xnz5iXzd955p+y1ydOkSZOSeadOnQqzRYsWJWtTM/s++eSTZG3r1q0LswcffDBZu/vuuxdm22yzTWE2cODA5LopM2bMSOZdu3YtzMw2pBwfffRRjax75plnJvOTTz65MFt33XWTtcszf/ell14qzA444IDCLDWnuDKVnc3p06cXZpXNMQbqRps2bZL5oYceWvbaI0aMKMwWL15c9roNlTvGAAAAZE1jDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1oxrysSvf/3rwqxVq1Zlr3vCCSck89dff73stWFZZs2aVSPr3nfffTWybkTEH//4x8Ls8ccfL8x+/OMfJ9dNjV/5xz/+kaw1konqlvo7lzoDEREHH3xwYbbWWmuVvaflMXTo0GSeel+dOXNmdW8nIiJmz56dzP/3f/+3MFue0YyvvPJKMv/iiy/KXhtysMkmmxRm//M//5Os3X777Quzv/3tb8naW265Jb0xvsYdYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALJmjnED0bt372Tes2fPstdOzY197LHHyl4XiNh3330Ls9Sc4oiIioqKwuzoo48ue09QjkWLFhVmJ598crL2888/L8yOOuqosvc0adKkZH722WcXZvfcc0+ytlQqlbOl5bJgwYJkft111xVmhxxySLJ2zpw5hdkee+yRrK1svjL8t4022iiZp+aIb7bZZsnaAw44oDB78803k7WtWrUqzLp06VKYde3aNbnuqaeeWpi1a9cuWZuaVfy9730vWfvZZ58lc77OHWMAAACypjEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrFaUqzhtIjQWhdjRu3Lgwe+GFF5K1m2++eWGWGpMRkR4F9fe//z1Zy7/VxWiPyjjXtaNZs2aF2UUXXVSYHX/88cl133rrrcKsshFt06ZNS+ZUjXMNDY9zXfP69OmTzB944IGy1164cGFhVtmfbep1btKkZqbcPvHEE8k8NdbROKaqq8q5dscYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArNXMQC5qxJ577lmYpeYUV2bGjBnJ3KxiWD577bVXYXb00UeXve6ll15amJlTDMCK6uGHH07mK6+8cmGWmusbEbHJJpsUZt26dUtvLGGdddYpzP785z8na++5557C7LXXXkvWLl68OL0xqo07xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNYqSqVSqUpPrKio6b1Qib/85S+F2e67756sTf0xH3jggcnakSNHpjdGlVTxqNUq57p2pM7QfvvtV5g988wzyXV32GGHsvdE9XCuoeFxrqHhqcq5dscYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArDWp6w1QdWuuuWZhVtlsrssuu6wwGzt2bLlbAqpg8uTJhdmsWbMKs+uvv74GdgMAwH9zxxgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMhaRamyOT9fPbGioqb3QiWeeuqpwuy+++5L1l5yySXVvR2+oSoetVrlXMPyca6h4XGuoeGpyrl2xxgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsmWMMtcRcRGh4nGtoeJxraHjMMQYAAIBKaIwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMhalcc1AQAAQEPkjjEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYAwAAkDWNMQAAAFnTGAMAAJA1jTEAAABZ0xgDAACQNY0xAAAAWdMYr4CmTJkSFRUVcckll1TbmmPHjo2KiooYO3Zsta0JVJ1zDQ2Pcw0Nj3OdL41xNbn11lujoqIiJkyYUNdbqRV77LFHVFRUxPHHH1/XW4Eak8O5fu+996J///6x6qqrxiqrrBLf//7346233qrrbUGNyeFc33XXXbHNNttE8+bNY/XVV48BAwbE9OnT63pbUGNyONfer2tek7reAPXPiBEj4qmnnqrrbQDLac6cObHLLrvEp59+GmeccUY0bdo0Lrvssth5553jhRdeiNVWW62utwh8Q9ddd138/Oc/j9122y0uvfTSmDp1alxxxRUxYcKEGDduXDRv3ryutwh8Q96va4fGmG9k7ty5ccopp8Svf/3rOOuss+p6O8ByuPbaa2Py5Mkxfvz42G677SIiYu+9944tttgihgwZEuedd14d7xD4JubPnx9nnHFGfPe7342//OUvUVFRERERPXv2jO9973tx4403xi9+8Ys63iXwTXm/rh2+lboWzZ8/P84666zYdttto02bNtGqVav4zne+E2PGjCmsueyyy6JTp07RokWL2HnnnWPixIlLPWfSpEnRr1+/aNeuXTRv3jy6d+8eo0aNqnQ/X3zxRUyaNOkbfXvVRRddFIsXL46BAwdWuQYasvp8ru+9997YbrvtlrzJRkR06dIldtttt7j77rsrrYeGqr6e64kTJ8asWbPi4IMPXtIUR0Tsu+++0bp167jrrrsqvRY0VPX1XEd4v64tGuNaNHv27LjpppuiV69eceGFF8bZZ58d06ZNi969e8cLL7yw1PNvv/32uPLKK+O4446L008/PSZOnBi77rprfPTRR0ue88orr8QOO+wQr732Wpx22mkxZMiQaNWqVfTt2zdGjhyZ3M/48eNjs802i6uvvrpK+3/33XfjggsuiAsvvDBatGjxjT52aKjq67levHhxvPTSS9G9e/elsh49esSbb74Zn332WdVeBGhg6uu5njdvXkTEMt+jW7RoEc8//3wsXry4Cq8ANDz19Vx7v65FJarFLbfcUoqI0jPPPFP4nIULF5bmzZv3tcc++eST0hprrFE66qijljz29ttvlyKi1KJFi9LUqVOXPD5u3LhSRJROOumkJY/ttttupW7dupXmzp275LHFixeXevbsWdp4442XPDZmzJhSRJTGjBmz1GODBw+u0sfYr1+/Us+ePZf8/4goHXfccVWqhfqoIZ/radOmlSKi9Lvf/W6p7JprrilFRGnSpEnJNaA+aujnuqKiojRgwICvPT5p0qRSRJQiojR9+vTkGlAfNfRz7f26drhjXIsaN24cK620UkT8619/Zs6cGQsXLozu3bvHc889t9Tz+/btG2uvvfaS/9+jR4/Yfvvt4+GHH46IiJkzZ8Zjjz0W/fv3j88++yymT58e06dPjxkzZkTv3r1j8uTJ8d577xXup1evXlEqleLss8+udO9jxoyJ++67Ly6//PJv9kFDA1dfz/WXX34ZERHNmjVbKvvql/N89RzITX091+3bt4/+/fvHbbfdFkOGDIm33nornnjiiTj44IOjadOmEeFck6/6eq69X9cejXEtu+2222LLLbeM5s2bx2qrrRarr756PPTQQ/Hpp58u9dyNN954qcc22WSTmDJlSkREvPHGG1EqlWLQoEGx+uqrf+2/wYMHR0TExx9/vNx7XrhwYfzyl7+MH//4x1/72QbgX+rjuf7qWy2/+tbL/zR37tyvPQdyVB/PdUTEDTfcEPvss08MHDgwNtxww/jud78b3bp1i+9973sREdG6detquQ7UR/XxXHu/rj1+K3UtGjZsWBxxxBHRt2/f+NWvfhUdOnSIxo0bx/nnnx9vvvnmN17vq58TGjhwYPTu3XuZz9loo42Wa88R//oZi3/84x9xww03LPlk8JXPPvsspkyZEh06dIiWLVsu97Wgvqmv57pdu3bRrFmz+OCDD5bKvnqsY8eOy30dqI/q67mOiGjTpk386U9/infffTemTJkSnTp1ik6dOkXPnj1j9dVXj1VXXbVargP1TX09196va4/GuBbde++90blz5xgxYsTXflvkV/+q9N8mT5681GOvv/56rL/++hER0blz54iIaNq0aey+++7Vv+H/7913340FCxbEt7/97aWy22+/PW6//fYYOXJk9O3bt8b2ACuq+nquGzVqFN26dYsJEyYslY0bNy46d+4cK6+8co1dH1Zk9fVc/6f11lsv1ltvvYiImDVrVjz77LNx4IEH1sq1YUVUX8+19+va41upa1Hjxo0jIqJUKi15bNy4cfHUU08t8/n333//1342Yfz48TFu3LjYe++9IyKiQ4cO0atXr7jhhhuW+a9I06ZNS+6nqr8m/gc/+EGMHDlyqf8iIvbZZ58YOXJkbL/99sk1oKGqr+c6IqJfv37xzDPPfO3N9h//+Ec89thjcdBBB1VaDw1VfT7Xy3L66afHwoUL46STTiqrHhqC+nyuvV/XDneMq9nNN98co0ePXurxE044Ifbdd98YMWJE7L///tGnT594++234/rrr4+uXbvGnDlzlqrZaKONYqeddopjjz025s2bF5dffnmsttpqceqppy55zjXXXBM77bRTdOvWLY455pjo3LlzfPTRR/HUU0/F1KlT48UXXyzc6/jx42OXXXaJwYMHJ3/wv0uXLtGlS5dlZhtssIE7xTR4DfFcR0T8/Oc/jxtvvDH69OkTAwcOjKZNm8all14aa6yxRpxyyilVf4GgHmqo5/qCCy6IiRMnxvbbbx9NmjSJ+++/P/785z/Hueee6/eE0OA11HPt/bp2aIyr2XXXXbfMx4844og44ogj4sMPP4wbbrghHnnkkejatWsMGzYs7rnnnhg7duxSNYcddlg0atQoLr/88vj444+jR48ecfXVV8daa6215Dldu3aNCRMmxG9/+9u49dZbY8aMGdGhQ4fYeuut46yzzqqpDxOy0lDP9corrxxjx46Nk046Kc4999xYvHhx9OrVKy677LJYffXVq+06sCJqqOe6W7duMXLkyBg1alQsWrQottxyy7j77rvdVSILDfVce7+uHRWl//x+AgAAAMiMnzEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACyVuU5xhUVFTW5D2jwVsTJaM41LB/nGhoe5xoanqqca3eMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADIWpO63gAAAEBdO+200wqzV199tTDbbrvtkuu2a9euMNtvv/2StW+++WZhdsQRRyRrp0yZksz5OneMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArBnXRKWaN29emP3P//xPsvbHP/5xYXbeeecla3/zm9+kNwa16Cc/+Ulhdv311ydrKyoqCrOpU6cWZrfcckty3bfffrswGzZsWLK2VCoVZgsXLkzWAkBDlHpPHjlyZNnrpr4OSL0fR0R07NixMHvuueeStS+++GJhduGFFyZrR48encwbIneMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyFpFqbLhWV89MTF/i/pto402SuZnnXVWYfajH/2o7OuOGjUqme+///5lr70iquJRq1XO9b+tscYayfzRRx8tzLp27Vrd21lub731VjL/4IMPCrOhQ4cma++4447CbPHixemNNTDONcvSuHHjwqxfv37J2mOOOaYw22yzzZK1M2fOLMx+85vfJGsre0/OiXOdryZNmhRmqa95b7755uS6yzPHuKZU9n6d+nwyY8aMZO2K+HVRVV5nd4wBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsGdfUQFT257PjjjsWZn/84x+TtR07dixrTxERCxcuLMwGDhyYrL3qqqvKvu6KyPiHutemTZvC7IknnkjWbr755oXZokWLkrU33nhjYbbNNtsUZpWNUmvatGlhtvLKKydrl8f2229fmD3//PPJ2speq/rGuW642rZtm8xPPvnkwuzAAw8szLp06ZJcd968eYXZuHHjkrWp9/o5c+YkazfddNPCbPr06cnahsa5zlefPn0Ks+UZaZb68/v444+TtbfeemthttpqqyVrN9xww8KsV69eydrUOUh9noqIeOCBBwqzU045JVk7derUZF4u45oAAACgEhpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAga+YY1yOrr756YXb88ccna88888zq3k6VvPPOO4VZ586da3Endc9cxLq31lprFWbLMzfvlVdeSeZbbrll2WunrLvuuoXZfffdl6xNnb/K5remVPaxVvZa1TfOdf3Wo0ePwuyRRx5J1q666qqF2XPPPVeY3XTTTcl1r7vuumSekpox/vTTTydrf/nLXxZmV111Vdl7qo+c6/qtXbt2hdldd92VrE3N9m3cuHG5W4onnniiMLvjjjuStUOHDi37uik/+9nPkvn3v//9wmzPPfcs+7qHHHJIMr/77rvLXjvFHGMAAACohMYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsNanrDfB13/3udwuzIUOGFGbbbLNNct1Zs2YVZpWNcvrJT35SmFU2muW2225L5kD5/vnPfxZmqTE0ERG9e/cuzP70pz8la5s2bVqYnXDCCcna1OcTqG5bbbVVMh89enRh1qZNm2Tt7bffXpj94he/KMxmz56dXHd5pM5mZd5+++1q3AnUnP333z+Z/+53vyvMNt988+reTkRE3H///cn8gAMOqJHrLo/rr7++7Pzee+9N1qY+3spGZtXUuKaqcMcYAACArGmMAQAAyJrGGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArJljXMu23XbbZH7EEUcUZqlZxY8//njZ6+6xxx7J2m7duhVm77//frL2nnvuSeZQm1ZbbbUaWbey+YUrokceeaQwu/jii5O1Z5xxRmG2yiqrJGubNCl+21m4cGGyFpalUaPif+MfNGhQsrZt27aF2Z///Odk7eGHH57eWB3o379/2bVbbLFFYfbkk08ma2fNmlX2dWFZOnToUJhdccUVydp11lmnMCuVSmXv6YUXXijMfvzjH5e9bn107rnnJvPKZk2vqNwxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAsmZcUxlWWmmlZP6rX/2qMDvttNOStYsWLSrMTjzxxMLs+uuvT66b+vX0xx13XLK2oqKiMHv00UeTta+++moyh9p07LHH1si6EydOrJF168rf//73ZD5//vzC7KCDDkrWpj6Pffjhh8laWJY2bdoUZgcccECydsaMGYXZ8ow+SmnatGky33nnnQuzyvZ02GGHlbWniIjzzz+/MOvSpUuyNjUSEpalVatWyfyhhx4qzNZee+2yrztv3rxk/uCDDxZmP/nJTwqzzz//vOw91UeffPJJXW+hRrhjDAAAQNY0xgAAAGRNYwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNbMMS6wzTbbFGZXXHFFsrZnz55lX3fPPfcszP7617+Wve5+++1XmH3rW99K1k6YMKEw+/Wvf132nqA+ef/99wuz//u//6vFndS8//3f/03m7777bmG20UYbJWtTc1ivvPLK9Magmq2yyiqF2amnnpqsnT59emG22WabFWbbbbddct2tttoqmdeFMWPG1PUWaGB+8YtfJPPU1+GVmTx5cmF2xhlnJGvvu+++sq+bkyOPPLLs2kceeaQad1K93DEGAAAgaxpjAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACylu24pi5duiTzhx56qDDr0KFDsvbzzz8vzA4++OBk7d/+9rdkXqRZs2bJ/NZbby1r3YiIP/zhD4XZrFmzkrU77bRTYdauXbtk7ejRo5N5uUqlUjJfsGBBjVyX+i11rt97771a3En9tvHGG9f1Fmhg5s2bV5ilxg1GRHTv3r0wq2ysS8rChQsLs1deeSVZe/HFFxdmb7/9drL22muvTW8s4fnnny/M7rzzzrLXJV977bVXYfa73/2uxq6bGv1nHFPVpf78jj322LLX/f3vf192bU1zxxgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKxpjAEAAMhagx7X1LJly8Lst7/9bbK2spFMKRUVFYXZgQcemKytLC/StWvXZN6mTZuy1o2IGDJkSFnZiurjjz9O5j169CjM/vnPf1b3dgBYDl988UVh1rNnz2Tt/vvvX5httdVWydrUCLf777+/MKtsXFPKtttuW3ZtZe64447CzBhDytGnT5/CrEmT8luQc889N5lfc801Za+dk8033zyZn3jiiYXZ6quvnqxNfX788MMPk7V1yR1jAAAAsqYxBgAAIGsaYwAAALKmMQYAACBrGmMAAACypjEGAAAgaxpjAAAAstag5xhvvPHGhVm/fv1q7Lqp+clHHnlkjV23vnn33XeT+aOPPloj133ssceS+YwZM2rkugDUrsrm7959991lZTWpefPmhdnAgQPLXveFF15I5ldffXXZa5OnNm3aJPNevXoVZqVSKVk7bdq0wux//ud/krVUzZAhQ5L5HnvsUZjNnTs3WXvEEUcUZm+++Wayti65YwwAAEDWNMYAAABkTWMMAABA1jTGAAAAZE1jDAAAQNY0xgAAAGStQY9reumllwqzwYMHJ2uPPfbYwmyVVVZJ1k6dOrUwW7hwYbL21VdfLcz23HPPsveU+rXql1xySbJ25MiRhdkrr7ySrE1ZvHhxMl+0aFHZawNAfZQa6/iDH/wgWfvll18WZpV93VPZaCv4b4MGDUrmXbt2Lcy++OKLZG2fPn0Ks9TX2blZaaWVkvnjjz9emO2www7J2tTnk1GjRiVr77vvvmS+onLHGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyJrGGAAAgKw16DnGpVKpMDv33HOTtTfffHNh1r59+2Tta6+9VphVNidwq622Ksx23nnnwiz1sUZEHH300YXZH/7wh2QtENGqVavCbJ111knW1reZi717907m6667btlrv/7662XXQkOw0UYbJfMrrrii7LV/85vfFGaVzR2Fb6pZs2Zl137++efJ/Nlnny177YamS5cuhdl1112XrO3Ro0dhVlnvcP311xdmJ598crK2vnLHGAAAgKxpjAEAAMiaxhgAAICsaYwBAADImsYYAACArGmMAQAAyFqDHte0PN5///2ysuV14IEHFmarr756YTZz5szkukYywfLp2LFjYdazZ89k7d13313d26lRbdu2TeapER1vvfVWsvaOO+4o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",
      "text/plain": [
       "<Figure size 1000x1000 with 16 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# Get 16 random indices\n",
    "indices = np.random.choice(len(train_dataset), size=16, replace=False)\n",
    "\n",
    "# Get the corresponding images and labels\n",
    "images = [train_dataset[i][0] for i in indices]\n",
    "labels = [train_dataset[i][1] for i in indices]\n",
    "\n",
    "# Create a figure with 4x4 subplots\n",
    "fig, axes = plt.subplots(4, 4, figsize=(10, 10))\n",
    "\n",
    "# Iterate over the subplots and display the images with labels\n",
    "for i, ax in enumerate(axes.flat):\n",
    "    ax.imshow(images[i].squeeze(), cmap='gray')\n",
    "    ax.set_title(f\"Label: {labels[i]}\")\n",
    "    ax.axis('off')\n",
    "\n",
    "# Adjust the spacing between subplots\n",
    "plt.tight_layout()\n",
    "\n",
    "# Show the figure\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Build the RNN Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "RNN(\n",
      "  (rnn): RNN(28, 256, batch_first=True)\n",
      "  (fc): Linear(in_features=256, out_features=10, bias=True)\n",
      ")\n",
      "h.shape: torch.Size([1, 64, 256])\n",
      "y.shape: torch.Size([64, 10])\n",
      "Number of parameters: 75,786\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "\n",
    "# Build an RNN model to classsify MNIST images\n",
    "class RNN(nn.Module):\n",
    "    def __init__(self, input_size=28, hidden_size=256, num_layers=1, num_classes=10):\n",
    "        super(RNN, self).__init__()\n",
    "        self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)\n",
    "        self.fc = nn.Linear(hidden_size, num_classes)\n",
    "\n",
    "    def forward(self, x):\n",
    "        # input is a \"sequence\" of 28 features w/ length 28\n",
    "        x = x.squeeze(1)\n",
    "        y, h = self.rnn(x) #, h0)\n",
    "        # retrieve the last element of the sequence\n",
    "        # print(\"y.shape:\", y.shape). # b, 28, 256\n",
    "        y = self.fc(y[:, -1, :]) # b, 10\n",
    "        return y, h\n",
    "\n",
    "\n",
    "x = torch.randn(64, 1, 28, 28)\n",
    "model = RNN()\n",
    "print(model)\n",
    "y , h = model(x)\n",
    "\n",
    "print(\"h.shape:\", h.shape)\n",
    "print(\"y.shape:\", y.shape)\n",
    "\n",
    "\n",
    "# print the number of parameters\n",
    "num_params = sum(p.numel() for p in model.parameters())\n",
    "# Use comma to print the number in a more readable format\n",
    "print(f\"Number of parameters: {num_params:,}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define the loss function and optimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "model.to(device)\n",
    "\n",
    "# Define the loss function and optimizer\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Train the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 10%|█         | 1/10 [00:19<02:59, 19.91s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 1, Loss: 0.5974\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 20%|██        | 2/10 [00:38<02:35, 19.40s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 2, Loss: 0.2706\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 30%|███       | 3/10 [00:58<02:15, 19.33s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 3, Loss: 0.2112\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 40%|████      | 4/10 [01:20<02:03, 20.58s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 4, Loss: 0.1791\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 50%|█████     | 5/10 [01:46<01:52, 22.49s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 5, Loss: 0.1534\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 60%|██████    | 6/10 [02:09<01:30, 22.62s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 6, Loss: 0.1454\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 70%|███████   | 7/10 [02:32<01:08, 22.78s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 7, Loss: 0.1380\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 80%|████████  | 8/10 [02:55<00:45, 22.74s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 8, Loss: 0.1303\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 90%|█████████ | 9/10 [03:18<00:23, 23.02s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 9, Loss: 0.1226\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 10/10 [03:42<00:00, 22.21s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 10, Loss: 0.1247\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "from tqdm import tqdm\n",
    "\n",
    "num_epochs = 10\n",
    "\n",
    "model.train()\n",
    "for epoch in tqdm(range(num_epochs)):\n",
    "    total_loss = 0\n",
    "    \n",
    "    for images, labels in train_loader:\n",
    "        images = images.to(device)\n",
    "        labels = labels.to(device)\n",
    "        \n",
    "        # Forward pass\n",
    "        outputs, _ = model(images)\n",
    "        loss = criterion(outputs, labels)\n",
    "        \n",
    "        # Backward and optimize\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        total_loss += loss.item()\n",
    "    \n",
    "    avg_loss = total_loss / len(train_loader)\n",
    "    print(f\"Epoch: {epoch+1}, Loss: {avg_loss:.4f}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Evaluate the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test Accuracy: 0.9681\n"
     ]
    }
   ],
   "source": [
    "model.eval()  # Set the model to evaluation mode\n",
    "\n",
    "correct = 0\n",
    "total = 0\n",
    "\n",
    "with torch.no_grad():\n",
    "    for images, labels in test_loader:\n",
    "        images = images.to(device)\n",
    "        labels = labels.to(device)\n",
    "        \n",
    "        # Forward pass\n",
    "        outputs, _ = model(images)\n",
    "        _, predicted = torch.max(outputs.data, 1)\n",
    "        \n",
    "        total += labels.size(0)\n",
    "        correct += (predicted == labels).sum().item()\n",
    "\n",
    "accuracy = correct / total\n",
    "print(f\"Test Accuracy: {accuracy:.4f}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Predict on sample images"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYUAAAGbCAYAAAAr/4yjAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAFoBJREFUeJzt3XmMVvX58OF7WERAlEVFBYoiIogaLYiKsrikhKKI1RIhLq0VXOMWRVtAtEYNtRa1rrVUVMRKlVKrVbuAdSlg3YhoESRowTQKKpsoiHPePxru9zcFZM6UYet1JSRy5tzP+c4MPJ85z3M4VhRFUQQARESdLb0AALYeogBAEgUAkigAkEQBgCQKACRRACCJAgBJFABIovA/7L333ouKior46U9/uske87nnnouKiop47rnnNtljbg8qKiri2muvzd+PGzcuKioq4r333ttia/pP/7lG/jeJwjZm7ZPJK6+8sqWXUmv+/Oc/xzHHHBO77rprNG3aNLp16xYPPfRQjR/v2muvjYqKivzVqFGjOOCAA2LEiBGxbNmyTbjy2jdhwoS49dZbt/Qy1mvu3Llx2mmnRevWraNRo0bRsWPH+PGPfxwrV67c0kujhHpbegHwfz3xxBMxYMCAOPLII/PJfOLEiXHmmWfG4sWL47LLLqvxY999992x0047xYoVK+KPf/xj3HDDDTFlypR46aWXoqKiYhN+Fht3xhlnxGmnnRYNGjQoNTdhwoSYNWtWXHrppbWzsBpasGBBdOvWLXbZZZe46KKLonnz5jFt2rQYNWpUvPrqq/G73/1uSy+RahIFtip33HFH7LnnnjFlypR8wjz33HOjY8eOMW7cuP8qCqeeemrsuuuuERFx3nnnxSmnnBKTJk2K6dOnx5FHHrnemZUrV0ajRo1qfMwNqVu3btStW3eTP+6W8tBDD8WSJUvixRdfjM6dO0dExNChQ6OysjIefPDB+PTTT6NZs2ZbeJVUh5ePtkOrV6+Oa665Jrp06RK77LJLNG7cOHr06BFTp07d4MyYMWOibdu20bBhw+jVq1fMmjVrnX1mz54dp556ajRv3jx23HHH6Nq1azzxxBMbXc/KlStj9uzZsXjx4o3uu2zZsmjWrFmVn6Dr1asXu+66azRs2HCj82Uce+yxERExf/78iIjo3bt3HHjggfHqq69Gz549o1GjRvGjH/0oIiJWrVoVo0aNivbt20eDBg2iTZs2MWzYsFi1alWVx1y1alVcdtllsdtuu0WTJk2if//+sXDhwnWOvaH3FJ5++uno1atXNGnSJHbeeec47LDDYsKECbm+p556Kt5///18KWzvvfeucuxNucaIf3/P//nPf270a7n2ZbiWLVtW2b7nnntGnTp1YocddtjoY7B1cKawHVq2bFn88pe/jEGDBsWQIUNi+fLlMXbs2OjTp0+8/PLLccghh1TZ/8EHH4zly5fHhRdeGF988UXcdtttceyxx8abb76Zf8nfeuutOOqoo6JVq1Zx9dVXR+PGjWPixIkxYMCAePzxx+Pkk0/e4HpefvnlOOaYY2LUqFEbfSOzd+/eMXr06Bg5cmScddZZUVFRERMmTIhXXnklJk6c+N9+aaqYN29eRES0aNEit3388cfRt2/fOO200+L000+Pli1bRmVlZfTv3z9efPHFGDp0aHTq1CnefPPNGDNmTMyZMycmT56c8+ecc06MHz8+Bg8eHN27d48pU6ZEv379qrWecePGxdlnnx2dO3eOH/7wh9G0adN4/fXX45lnnonBgwfH8OHDY+nSpbFw4cIYM2ZMRETstNNOERG1tsZOnTpFr169NnrhwNrv2w9+8IO47rrrokWLFvG3v/0t7r777rj44oujcePG1foasBUo2Kbcf//9RUQUf//73ze4z5o1a4pVq1ZV2fbpp58WLVu2LM4+++zcNn/+/CIiioYNGxYLFy7M7TNmzCgiorjsssty23HHHVccdNBBxRdffJHbKisri+7duxf77bdfbps6dWoREcXUqVPX2TZq1KiNfn4rVqwoBg4cWFRUVBQRUURE0ahRo2Ly5Mkbnd2QUaNGFRFRvPPOO8WiRYuK+fPnF/fee2/RoEGDomXLlsVnn31WFEVR9OrVq4iI4p577qky/9BDDxV16tQpXnjhhSrb77nnniIiipdeeqkoiqJ44403iogoLrjggir7DR48eJ3Pf+33cf78+UVRFMWSJUuKJk2aFIcffnjx+eefV5mvrKzM/+7Xr1/Rtm3bdT7H2lhjURRFRBS9evVa53jrc/311xcNGzbM71tEFMOHD6/WLFsPLx9th+rWrZun65WVlfHJJ5/EmjVromvXrvHaa6+ts/+AAQOiVatW+ftu3brF4YcfHn/4wx8iIuKTTz6JKVOmxMCBA2P58uWxePHiWLx4cXz88cfRp0+fmDt3bnzwwQcbXE/v3r2jKIpqXe7YoEGD6NChQ5x66qnxyCOPxPjx46Nr165x+umnx/Tp00t+Jaraf//9Y7fddot99tknzj333Gjfvn089dRTVd4zaNCgQXz/+9+vMveb3/wmOnXqFB07dszPffHixfny09qX5dZ+vS6++OIq89V5U/hPf/pTLF++PK6++urYcccdq3ysOm+C19Yai6Ko9uXFe++9d/Ts2TN+8YtfxOOPPx5nn3123HjjjXHHHXdUa56tg5ePtlMPPPBA3HLLLTF79uz48ssvc/s+++yzzr777bffOts6dOiQL9e8++67URRFjBw5MkaOHLne43300UdVwlJTF110UUyfPj1ee+21qFPn3z+zDBw4MDp37hyXXHJJzJgxo8aP/fjjj8fOO+8c9evXj9atW8e+++67zj6tWrVa5/XvuXPnxj/+8Y/Ybbfd1vu4H330UUREvP/++1GnTp11Hnf//fff6NrWvpR14IEHVutz+U+bY41f59e//nUMHTo05syZE61bt46IiO985ztRWVkZV111VQwaNKjKy3RsvURhOzR+/Pj43ve+FwMGDIgrr7wydt9996hbt27cdNNN+eRTRmVlZUREXHHFFdGnT5/17tO+ffv/as0R/36DfOzYsTFs2LAMQkRE/fr1o2/fvnHHHXfE6tWra/ymZc+ePfPqow1Z35vZlZWVcdBBB8XPfvaz9c60adOmRuvZlLb0Gu+666449NBDMwhr9e/fP8aNGxevv/56HH/88bW6BjYNUdgOPfbYY9GuXbuYNGlSlZceRo0atd79586du862OXPm5JUt7dq1i4h/PznX5l/sjz/+ONasWRNfffXVOh/78ssvo7Kycr0fq2377rtvzJw5M4477rivfSmnbdu2UVlZGfPmzavyk/c777xTrWNERMyaNetrA7uh42+ONX6dDz/8cL2XnK49S12zZs1/9fhsPt5T2A6tvf69KIrcNmPGjJg2bdp69588eXKV9wRefvnlmDFjRvTt2zciInbffffo3bt33HvvvfGvf/1rnflFixZ97Xqqe0nq7rvvHk2bNo3f/va3sXr16ty+YsWK+P3vfx8dO3bc5JelVsfAgQPjgw8+iPvuu2+dj33++efx2WefRUTk1+v222+vsk91/gXyt771rWjSpEncdNNN8cUXX1T52P/9PjZu3DiWLl262dZY3UtSO3ToEK+//nrMmTOnyvZHHnkk6tSpEwcffPBGH4OtgzOFbdSvfvWreOaZZ9bZfskll8QJJ5wQkyZNipNPPjn69esX8+fPj3vuuScOOOCAWLFixToz7du3j6OPPjrOP//8WLVqVdx6663RokWLGDZsWO5z5513xtFHHx0HHXRQDBkyJNq1axcffvhhTJs2LRYuXBgzZ87c4Fqre0lq3bp144orrogRI0bEEUccEWeeeWZ89dVXMXbs2Fi4cGGMHz++yv69e/eOv/71r1WeNGvDGWecERMnTozzzjsvpk6dGkcddVR89dVXMXv27Jg4cWI8++yz0bVr1zjkkENi0KBBcdddd8XSpUuje/fu8Ze//CXefffdjR5j5513jjFjxsQ555wThx12WAwePDiaNWsWM2fOjJUrV8YDDzwQERFdunSJRx99NC6//PI47LDDYqeddooTTzyx1tZY3UtSr7zyynj66aejR48ecdFFF0WLFi3iySefjKeffjrOOeec2GuvvUp/3dlCtuSlT5S39lLGDf1asGBBUVlZWdx4441F27ZtiwYNGhSHHnpo8eSTTxZnnXVWlcsZ116SevPNNxe33HJL0aZNm6JBgwZFjx49ipkzZ65z7Hnz5hVnnnlmscceexT169cvWrVqVZxwwgnFY489lvv8t5ekFkVRPPzww0W3bt2Kpk2bFg0bNiwOP/zwKsdYq0uXLsUee+yx0cdbe0nqokWLvna/Xr16FZ07d17vx1avXl2MHj266Ny5c9GgQYOiWbNmRZcuXYrrrruuWLp0ae73+eefFxdffHHRokWLonHjxsWJJ55YLFiwYKOXpK71xBNPFN27dy8aNmxY7LzzzkW3bt2KRx55JD++YsWKYvDgwUXTpk2LiKjy/dzUayyKcpekzpgxo+jbt2/++ejQoUNxww03FF9++WW15tk6VBRFLf+YBbVg+fLl0bx587j11lvjwgsv3NLLge2G9xTYJj3//PPRqlWrGDJkyJZeCmxXnCkAkJwpAJBEAYAkCgAkUQAgVfsfr23u/10hAJtWda4rcqYAQBIFAJIoAJBEAYAkCgAkUQAgiQIASRQASKIAQBIFAJIoAJBEAYAkCgAkUQAgiQIASRQASKIAQBIFAJIoAJBEAYAkCgAkUQAgiQIASRQASKIAQBIF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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import random\n",
    "\n",
    "# Set the model to evaluation mode\n",
    "model.eval()\n",
    "\n",
    "# Select a random image from the test dataset\n",
    "random_index = random.randint(0, len(test_dataset) - 1)\n",
    "image, label = test_dataset[random_index]\n",
    "\n",
    "# Move the image to the device\n",
    "image = image.to(device)\n",
    "\n",
    "# Forward pass to get the predicted label\n",
    "output, _ = model(image.unsqueeze(0))\n",
    "_, predicted_label = torch.max(output, 1)\n",
    "\n",
    "# Convert the image tensor to a numpy array\n",
    "image_np = image.cpu().numpy()\n",
    "\n",
    "# Display the image, its label, and the predicted label\n",
    "plt.imshow(image_np.squeeze(), cmap='gray')\n",
    "plt.title(f\"Label: {label}, Predicted: {predicted_label.item()}\")\n",
    "plt.axis('off')\n",
    "plt.show()\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "agents",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
